'''
封装对发送接口请求前接口数据处理的操作,包括热加载和规范提取测试用例数据
'''
import re
import allure
import json
import jsonpath
import requests

from conf.conf_opertion import ConfOpertion
from Common.yaml_opertion import YamlOpertion
from Common.send_requests import SendRequests
from Common.debug_talk import DebugTalk
from Common.log_opertion import logs
from Common.assertions import Assertions
from base.generate_id import t_id
class ApiOpertion:
    def __init__(self):
        self.confot=ConfOpertion()
        self.sr=SendRequests()
        self.debugtalk=DebugTalk()
        self.logs=logs
        self.yo=YamlOpertion()
        self.ast=Assertions()
    # 1.yaml文件用例的热加载功能
    def hot_load(self, yaml_data):
        # 方法内部全局字符串变量
        str_yd = ''
        # 因为yaml用例文件默认是列表结构需要先取出字典
        if yaml_data and isinstance(yaml_data, list):
            yaml_data = yaml_data[0]
            self.logs.error('请传入字典或者字符串!')

        if yaml_data and isinstance(yaml_data, dict):
            str_yd = json.dumps(yaml_data, ensure_ascii=False)

        else:
            self.logs.error('数据既不是字典也不是字符串!!!')

        for i in range(str_yd.count('${')):
            if '${' in str_yd and '}' in str_yd:
                start_index = str_yd.index('${')
                # 记住这里的index('}',start_index)--->因为end_index要从start_index后面开始找第一个 } 要不然end_index有可能就在start_index前面
                end_index = str_yd.index('}', start_index)

                # 注意!完整的值:--> "${token}" <--是old_value,接下来要被整个替换掉---方便下一个循环寻找新的值
                old_value = str_yd[start_index:end_index + 1]
                # 在${}中不存在()代表不存在--->方法的热加载,只用做值的热加载
                if not '(' in str_yd and ')' in old_value:
                    # 目前只是变量名
                    new_value = str_yd[start_index + 2:end_index]

                # 代表方法热加载
                else:
                    # 目前只是方法调用名---get_extract_data(token)
                    function = str_yd[start_index + 2:end_index]
                    func_strat_index = function.index('(')
                    func_end_index = function.index(')')

                    # 代表get_extract_data
                    function_name = function[:func_strat_index]
                    # 代表方法传参token
                    function_value = function[func_strat_index + 1:func_end_index]
                    if function_value and ',' in function_value:
                        function_value=function_value.split(',')
                        new_value = getattr(self.debugtalk, function_name)(*function_value)
                    else:
                        # 替换成功的值
                        if function_value:
                            new_value = getattr(self.debugtalk, function_name)(function_value)
                        else:
                            new_value = getattr(self.debugtalk, function_name)()
                        # 需要判断new_value得到的到底是什么类型的值,否则不为str不可以直接调用replace
                        # 是字典或者列表,用json.dumps转化为字符串,确保是有效的json字符串序列,使其转换回json字典时可以被成功转换

                        if isinstance(new_value,dict) or isinstance(new_value,list):
                            # 是列表或者字典的话,要扩展old_vlue外面的引号
                            # 原old_value-->cokie:"${cookie}"中的${cookie},但由于替换后的new_value值为{}或[],
                            # 不把这个引号一起包括进old_value让new_value替换掉的话,格式会在loads转换错误
                            old_value = str_yd[start_index - 1:end_index + 2]
                            new_value = json.dumps(new_value)

                        elif isinstance(new_value, str):  # 明确检查字符串类型
                            """其余不是字符串转为字符串,是字符串不变动
                            原old_value-->cokie:"${token}"中的${token},但由于替换后的new_value值为字符串,
                            不可以把这个引号一起包括进old_value让new_value替换掉,否则字符串格式被破坏,格式会在loads转换错误
                            字符串处理分支"""
                            old_value = str_yd[start_index:end_index + 1]
                            new_value = new_value  # 无需转换
                        else:  # 其他类型（如数字、None等）
                            old_value = str_yd[start_index:end_index + 1]
                            new_value = str(new_value)  # 强制转为字符串
                    # 替换热加载值
                    str_yd=str_yd.replace(old_value,new_value)
                    self.logs.info(f'str_yd:{str_yd}')
        if yaml_data and isinstance(yaml_data, dict):
                try:
                    yaml_data = json.loads(str_yd)
                    return yaml_data
                except json.JSONDecodeError as e:
                    print(f"JSON解析失败: {e}\n原始字符串: {str_yd}")
                    # 返回原始字典避免后续错误
                    return yaml_data


    # 2.读取,处理,规范yaml文件数据
    def standardize_yaml(self,base_info,test_case):
        """
        对yaml_data进行规范处理
        :param yaml_data:用例数据
        :return:
        """

        if '${' in json.dumps(base_info):
            base_info=self.hot_load(base_info)
        if '${' in json.dumps(test_case):
            test_case=self.hot_load(test_case)
        base_url=self.confot.get_api_envi_value('host')

        api_name=base_info['api_name']
        url=base_url+base_info['url']
        method=base_info['method']
        header=base_info['header']
        # 判断用例是否携带cookie
        cookies=None
        if 'cookies' in base_info.keys():
            cookies=base_info['cookies']

         # test_case--{'case_name': '用户名密码正确', 'data': {'user_name': 'test01', 'passwd': 'admin123'},
         # 'validation': {'contains': {'msg': '登陆成功'}}, 'extract': {'token': '${get_extract_data(token)}'}}
        case_name=test_case['case_name']
        if 'validation' in test_case.keys():
            validation=test_case['validation']
            allure.attach(body=str(validation), name=f'断言:{validation}')
        else:
            validation=None

        # 处理文件上传
        # 结构:{files:{file:./data/heimingdan_online.xlsx}}
        if 'files' in test_case.keys():
            # fk-->file  fv-->文件路径
            files=test_case['files']
            self.logs.info(f'文件上传数据:{files}')
            for fk,fv in test_case['files'].items():
                # 二进制读取文件
                with open(fv,mode='rb') as f:
                    files={fk:f}
                    """此时的f(也就是fk对应的值应该为什么格式最好)最推荐格式:
                    元组(filename, file_obj, content_type)
                    filename：文件名（字符串，如"test.jpg"）。
                    file_obj：文件对象（通过open(file_path, "rb")获取的二进制流）
                    content_type（可选）：文件类型（如"image/jpeg"）。"""
        else:
            files=None

        # 判断三个参数哪一个存在,就调用对应的传参并请求
        if 'params' in test_case.keys():
            params=test_case['params']
            res=self.sr.run_main(params=params,url=url,method=method,apiname=api_name,casename=case_name,headers=header,verify=False,file=files,cookies=cookies)
            allure.attach(body=str(params),name=f'请求参数params')
            self.logs.info(f'请求参数params:{params}')
        elif 'data' in test_case.keys():
            data=test_case['data']
            res=self.sr.run_main(data=data,url=url,method=method,apiname=api_name,casename=case_name,headers=header,verify=False,file=files,cookies=cookies)
            allure.attach(body=str(data), name=f'请求参数data')
            self.logs.info(f'请求参数data:{data}')
        elif 'json' in test_case.keys():
            jsons=test_case['json']
            res=self.sr.run_main(json=jsons,url=url,method=method,apiname=api_name,casename=case_name,headers=header,verify=False,file=files,cookies=cookies)
            allure.attach(body=str(jsons), name=f'请求参数json')
            self.logs.info(f'请求参数json:{jsons}')
        else:
            res = self.sr.run_main( url=url, method=method, apiname=api_name, casename=case_name,headers=header, verify=False, file=files, cookies=cookies)

        # 得到返回值调用接口关联提取
        if 'extract' in test_case.keys():
            # 调用接口关联处理返回值
            self.res_write_extract(test_case['extract'], res)
        if 'extract_list' in test_case.keys():
            # 调用接口关联处理返回值
            self.res_write_extract_list(test_case['extract_list'], res)
        if validation and 'validation' in test_case.keys():
            self.ast.validations(validation=validation,response=res)
            # 每组测试数据日志结束
            self.logs.info(f'断言:{validation}')

        self.logs.info(f'接口响应结果:{res.text}')
        allure.attach(body=res.text, name=f'接口响应结果')
        return res

    # 3.用于做接口关联extarct值写入操作
    def res_write_extract(self,testcase_extract:dict,response:requests.models.Response):
        str_response=''
        # 定义一个用于作判断的正则表达式列表
        pattern_list=['(.*?)','(.+?)','(.*)',r'(\d+)',r'(\d*)']
        for key,value in testcase_extract.items():
            try:
                # 1.识别是否用'正则表达式'提取返回值中需要关联值
                for pat in pattern_list:
                    if pat in value:
                        if not isinstance(response, str):
                            str_response = response.text
                        write_data = {}
                        write_value=re.search(value,str_response).group(1)
                        if write_value:
                            # 如果正则提取的是整数如:200 则需要转为整形
                            if pat in [r'(\d+)',r'(\d*)']:
                                write_value=int(write_value)
                            write_data[key]=write_value
                            self.yo.write_yaml(value=write_data)
                            self.logs.info(f'数据:{write_data} 由正则提取写入extract.yml文件成功!')
                        else:
                            self.logs.error('正则提取返回值错误,可能返回值为空或者正则表达式不正确导致无法匹配!')
                if '$' in value:
                    write_data = {}
                    write_value=jsonpath.jsonpath(response.json(),value)[0]
                    if write_value:
                        write_data[key] = write_value
                        self.yo.write_yaml(value=write_data)
                        self.logs.info(f'数据:{write_data} 由json提取写入extract.yml文件成功!')
                    else:
                        self.logs.error('jsonpath提取返回值为错误,可能返回值为空或者jsonpath表达式不正确导致无法匹配!')
            except Exception as e:
                self.logs.error(f'接口返回值提取异常!请检查yaml用例文件中extract表达式是否出错.异常:{e}')


    # 4.用于做接口关联extarct_list值写入操作
    def res_write_extract_list(self,testcase_extract:dict,response:requests.models.Response):
        str_response=''
        # 定义一个用于作判断的正则表达式列表
        pattern_list=['(.*?)','(.+?)','(.*)',r'(\d+)',r'(\d*)']
        for key, value in testcase_extract.items():
            try:
                # 1.识别是否用'正则表达式'提取返回值中需要关联值
                for pat in pattern_list:
                    if pat in value:
                        if not isinstance(response, str):
                            str_response = response.text
                        write_data = {}
                        # 不一样的地方,会使用findall提取list表格作为写入extract表格值
                        write_value = re.findall(value, str_response,re.S)
                        if write_value:
                            write_data[key] = write_value
                            self.yo.write_yaml(value=write_data)
                            self.logs.info(f'数据:{write_data} 由正则提取写入extract.yml文件成功!')
                        else:
                            self.logs.error('正则提取返回值错误,可能返回值为空或者正则表达式不正确导致无法匹配!')
                if '$' in value:
                    write_data = {}
                    # 因为我们要查找的是列表结果并直接写入,次数不用将jsonpath找到的列表结果再次解包如[0]这样,直接返回列表结果即可
                    write_value=jsonpath.jsonpath(response.json(),value)
                    if write_value:
                        write_data[key] = write_value
                        self.yo.write_yaml(value=write_data)
                        self.logs.info(f'数据:{write_data} 由json提取写入extract.yml文件成功!')
                    else:
                        self.logs.error('json提取返回值为错误,可能返回值为空或者json表达式不正确导致无法匹配!')
            except Exception as e:
                self.logs.error(f'接口返回值提取异常!请检查yaml用例文件中extract表达式是否出错.异常:{e}')
if __name__ == '__main__':
    api=ApiOpertion()
    yo=YamlOpertion()


    cookie={"access_token_cookie": "eyJhbGciOiJkQykUzb_P6ZRS6pRM0"}
    str_cookie=json.dumps(cookie)
    print(str_cookie)
    dict_cookie=json.loads(str_cookie)
    print(dict_cookie,type(dict_cookie))